import static org.junit.Assert.*;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

/**
 * 
 */

/**
 * COSC 617 Project 4 Task 4
 * 
 * @author Hui Liu
 *
 */
public class VendingMachineTest {
	public VendingMachine sodaMachine;
	public VendingMachineItem item;

	/**
	 * @throws java.lang.Exception
	 */
	@Before
	public void setUp() throws Exception {
		sodaMachine = new VendingMachine();
		item= new VendingMachineItem("Diet Pepsi", 1.5);
	}

	/**
	 * @throws java.lang.Exception
	 */
	@After
	public void tearDown() throws Exception {
		sodaMachine = null;
		item = null;
	}

	/**
	 * Test method for {@link VendingMachine#addItem(VendingMachineItem, java.lang.String)}.
	 */
	@Test
	public final void testAddItem() {
		//slot is empty
		assertNull(sodaMachine.getItem(VendingMachine.A_CODE));
		
		//Add a null item
		sodaMachine.addItem(null, VendingMachine.A_CODE);
		assertNull(sodaMachine.getItem(VendingMachine.A_CODE));

		//Item is added correctly
		sodaMachine.addItem(item, VendingMachine.A_CODE);
		assertSame(item, sodaMachine.getItem(VendingMachine.A_CODE));
		
		//check item didn't put into wrong slot
		assertNull(sodaMachine.getItem(VendingMachine.B_CODE));
		assertNull(sodaMachine.getItem(VendingMachine.C_CODE));
		assertNull(sodaMachine.getItem(VendingMachine.D_CODE));
		
	}

	/**
	 * Test method for {@link VendingMachine#addItem(VendingMachineItem, java.lang.String)}.
	 * If you add an item to a slot that is already occupied, VendingMachineException will be throwed
	 */
	@Test(expected=VendingMachineException.class)
	public final void testSlotOccupied() {
		//Add multiple item in one slot, 
		sodaMachine.addItem(item, VendingMachine.A_CODE);
		sodaMachine.addItem(item, VendingMachine.A_CODE);
	}

	/**
	 * Test method for {@link VendingMachine#addItem(VendingMachineItem, java.lang.String)}.
	 * If you add an item with an invalid code, VendingMachineException will be thrown
	 */
	@Test(expected=VendingMachineException.class)
	public final void testAddInvalidCode() {
		//Add item with an invalid code 
		sodaMachine.addItem(item, "slot");
	}

	/**
	 * Test method for {@link VendingMachine#removeItem(java.lang.String)}.
	 */
	@Test
	public final void testRemoveItem() {
		
		sodaMachine.addItem(item, VendingMachine.A_CODE);
		//Item is removed correctly
		VendingMachineItem removedItem = sodaMachine.removeItem(VendingMachine.A_CODE);
		//removed item is the correct item occupying the slot with the given code.
		assertSame(item, removedItem);
		//slot is empty now
		assertNull(sodaMachine.getItem(VendingMachine.A_CODE));
	}

	/**
	 * Test method for {@link VendingMachine#addItem(VendingMachineItem, java.lang.String)}.
	 * If you remove item from the slot at the specified code is empty,
	 * VendingMachineException will be thrown
	 */
	@Test(expected=VendingMachineException.class)
	public final void testRemoveEmptySlot() {
		//remove item from an empty slot
		sodaMachine.removeItem(VendingMachine.A_CODE);
	}

	/**
	 * Test method for {@link VendingMachine#addItem(VendingMachineItem, java.lang.String)}.
	 * If you remove item from the slot with invalid code,
	 * VendingMachineException will be thrown
	 */
	@Test(expected=VendingMachineException.class)
	public final void testRemoveInvalidCode() {
		//remove item from the slot with invalid code
		sodaMachine.removeItem(null);
	}

	/**
	 * Test method for {@link VendingMachine#insertMoney(double)}.
	 * If insert positive money, balance should be the previous balance + amount.
	 */
	@Test
	public final void testInsertMoney() {
		//get current balance
		double balance = sodaMachine.balance;
		//insert amount==0
		sodaMachine.insertMoney(0);
		//balance now should be the previous balance + amount.
		assertEquals(balance, sodaMachine.balance, 0.0001);
		
		//insert amount>0
		sodaMachine.insertMoney(300.1);
		//balance now should be the previous balance + amount.
		assertEquals(balance+300.1, sodaMachine.balance, 0.0001);
		
	}

	/**
	 * Test method for {@link VendingMachine#insertMoney(double)}.
	 * If insert negative money,
	 * VendingMachineException will be thrown
	 */
	@Test(expected=VendingMachineException.class)
	public final void testInsertNegativeMoney() {
		//insert amount<0
		sodaMachine.insertMoney(-10);
	}


	/**
	 * Test method for {@link VendingMachine#getBalance()}.
	 */
	@Test
	public final void testGetBalance() {
		//vending machine should start with a 0 balance
		assertEquals(0, sodaMachine.getBalance(), 0.0001);
		//test balance > 0
		sodaMachine.insertMoney(300.1);
		double balance = sodaMachine.balance;
		//vending machine should return the amount of balance
		assertEquals(balance, sodaMachine.getBalance(), 0.0001);
		//balance should remains the same as it was before the function was called.
		assertEquals(balance, sodaMachine.balance, 0.0001);
		
	}

	/**
	 * Test method for {@link VendingMachine#makePurchase(java.lang.String)}.
	 */
	@Test
	public final void testMakePurchase() {
		
		sodaMachine.addItem(item, VendingMachine.A_CODE);
		
		//test not enough money
		assertFalse(sodaMachine.makePurchase(VendingMachine.A_CODE));
		
		//test money == item.price
		sodaMachine.insertMoney(item.getPrice());
		//Returns true if there is enough money to make the purchase
		assertTrue(sodaMachine.makePurchase(VendingMachine.A_CODE)); 
		//The amount of the item is subtracted from the balance
		assertEquals(0, sodaMachine.balance, 0.0001); 
		//the item has been removed
		assertNull(sodaMachine.getItem(VendingMachine.A_CODE));

		//test money > item.price
		sodaMachine.addItem(item, VendingMachine.B_CODE);
		sodaMachine.insertMoney(item.getPrice()+5.0);
		//Returns true if there is enough money to make the purchase
		assertTrue(sodaMachine.makePurchase(VendingMachine.B_CODE)); 
		//The amount of the item is subtracted from the balance
		assertEquals(5.0, sodaMachine.balance, 0.0001); 
		//the item has been removed
		assertNull(sodaMachine.getItem(VendingMachine.B_CODE));

		
		//test empty slot
		assertFalse(sodaMachine.makePurchase(VendingMachine.B_CODE));
		
		//test invalid slot code
		assertFalse(sodaMachine.makePurchase("test"));
		
		//test null slot code
		assertFalse(sodaMachine.makePurchase(null));
		
	}

	/**
	 * Test method for {@link VendingMachine#returnChange()}.
	 */
	@Test
	public final void testReturnChange() {
		//test when balance=0
		assertEquals(0, sodaMachine.returnChange(), 0.0001);
		//test when balance > 0
		sodaMachine.addItem(item, VendingMachine.A_CODE);
		sodaMachine.insertMoney(100);
		sodaMachine.makePurchase(VendingMachine.A_CODE);
		//Returns: The amount of change in the machine
		assertEquals(100-item.getPrice(), sodaMachine.returnChange(), 0.0001);
		//Postcondition: balance = 0
		assertEquals(0, sodaMachine.balance, 0.0001);
	}

}
